perf(SegmentedControl): replace :has(+ [data-selected]) divider rule with adjacent-sibling selector#7903
perf(SegmentedControl): replace :has(+ [data-selected]) divider rule with adjacent-sibling selector#7903mattcosta7 wants to merge 2 commits into
:has(+ [data-selected]) divider rule with adjacent-sibling selector#7903Conversation
…with adjacent-sibling selector
🦋 Changeset detectedLatest commit: 83d8e52 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
There was a problem hiding this comment.
Pull request overview
This PR improves SegmentedControl CSS selector performance by removing a sibling-direction :has() selector used to hide the inter-item divider adjacent to the selected item, replacing it with constant-time adjacent-sibling combinators while keeping the divider’s visual position and behavior unchanged.
Changes:
- Move the inter-item divider from
.Item::after(non-last items) to.Item::before(non-first items). - Replace the
:has(+ [data-selected])-based “hide divider next to selection” rule with.Item[data-selected] + .Item::beforeplus a selected-item rule. - Add a patch changeset documenting the internal CSS change and rationale.
Show a summary per file
| File | Description |
|---|---|
| packages/react/src/SegmentedControl/SegmentedControl.module.css | Reworks divider rendering to avoid :has(+ …) and uses adjacent-sibling selectors to hide dividers near the selected item. |
| .changeset/perf-segmentedcontrol-divider-no-has.md | Adds a patch changeset describing the internal divider selector change and expected no-visual-diff outcome. |
Copilot's findings
- Files reviewed: 2/2 changed files
- Comments generated: 0
Closes #
Replaces the
&:has(+ [data-selected])::afterselector that hid the inter-item divider sitting just before the selected item. Previously the engine had to re-test a sibling-direction:has()against every.Itemon every selection change.Approach: flip the divider's side.
Drawing the divider on the leading edge of each non-first item (rather than the trailing edge of each non-last item) makes "the divider next to the selected item" expressible as adjacent-sibling combinators only:
The same 1px line lives in the same gap between any two adjacent items — visually identical. The hide rules become two plain combinator selectors that the engine resolves in constant time per-element on a selection change, with no
:has()walk.The companion
.Button:focus:focus-visible:not(:last-child)::afterrule a few lines below is a pre-existing no-op on.Button::after(which has nocontent) and is left alone; it never targeted.Item::after.Changelog
Changed
SegmentedControlinter-item divider is drawn on.Item::beforeof each non-first item instead of.Item::afterof each non-last item.Removed
&:has(+ [data-selected])::afterselector fromSegmentedControl.module.css.Rollout strategy
Testing & Reviewing
packages/react/src/SegmentedControl/**unit tests pass.tsc --noEmitonpackages/reactis clean.VRT expectations: None. The divider's geometry is unchanged — same 1px line at the same horizontal position, just attached to the item on its right rather than the item on its left. Hide-on-selection behavior is bit-identical (selected item and its trailing neighbour both lose their leading divider, mirroring the previous "item before selected + selected both lose their trailing divider").
Part of the
:has()-reduction series: see also #7901 (PageHeader) and #7902 (ActionList SubGroup active indicator).Merge checklist